﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Sinacor.OMM.ConnectionManager.MSMQ;
using System.Messaging;
using Sinacor.OMM.Logging;
using Sinacor.OMM.DumpEntity;
using Sinacor.OMM.HealthMonitor.Collector;
using Sinacor.OMM.HealthMonitor.Entity;
using Sinacor.OMM.HealthMonitor.Collector.SharedHealthDataMemoryStorage.Dumper;
using System.Diagnostics;
using System.Threading;
using System.Net;

namespace Sinacor.OMM.DumpServer
{
    public class DumperMain : IDisposable
    {
        private MsmqReceiver _msmqReceiver;

        private HealthMonitorCollector _healthMonitorCollector;
        private DumperShdms _dumperShdms;

        private DateTime _lastHealthStatusChecked = DateTime.Now;
        private int _totalMessageSentPerInterval = 0;
        private int _messagesPerSecond = 0;
        private const int LIMIT_HANDLE_COUNT = 900;

        public DumperMain()
        {
            //Inicializa área de memória compartilhada onde ficarão as informações sobre a saúde do sistema
            _dumperShdms = DumperShdms.GetSharedStorage();
            _dumperShdms.DumperDataInfo.ProcessDataInfo.LimitHandleCount = LIMIT_HANDLE_COUNT;
        }

        public void Run()
        {
            try
            {                
                //Inicializa o coletor de informações sobre a saúde do sistema, 
                //que ficará de tempos em tempos requisitando uma cópia do objeto em memória com os dados atualizados
                //para enviar à fila do health status monitor
                _healthMonitorCollector = HealthMonitorCollector.GetInstance();
                _healthMonitorCollector.AddHealthRequestListener(_healthMonitorCollector_HealthRequestListener, HealthType.Dumper);
                
                //Incializa o receptor de mensagens que recebe da fila MSMQ os conteúdos que devem ser escritos em disco
                _msmqReceiver = new MSMQHelper().CreateRepositoryMsmqReceiver(OMM.DumpConfiguration.DumpConfigurationManager.DumperConfiguration.MessageRepository);
                _msmqReceiver.MessageReceived += new MsmqReceiver.MessageArrival(_msmqReceiver_MessageReceived);
                _msmqReceiver.OnHealthStatusChanged += new MsmqReceiver.HealthStatusChanged(_msmqReceiver_OnHealthStatusChanged);

            }
            catch (Exception ex)
            {
                EventLog.WriteEntry(LogSources.DumperService, string.Concat(ex.Message, " ", ex.StackTrace), EventLogEntryType.Error);
            }
        }

        void _msmqReceiver_MessageReceived(Message msg, PSE.Framework.Messaging.MessageReceivedEventArgs e)
        {            
            DumpType.Type type;
            FlatFileWriter flatFileWriter;

            try
            {
                // Determina qual o tipo de Dump deve ser feito
                type = DumpType.ConvertFromInt(Int32.Parse(msg.Label));

                switch (type)
                {
                    // Faz o dump exato da string recebida, pulando uma linha a cada mensagem
                    case DumpType.Type.DumpLine:
                        DumpLineData dumpLineData = (DumpLineData)msg.Body;
                        flatFileWriter = DumpWriter.GetInstance(dumpLineData.FolderName, dumpLineData.FileName, dumpLineData.Type);
                        e.Result = flatFileWriter.Write(dumpLineData.DataToDump, dumpLineData.TimeStamp);
                        break;

                    // Faz o dump exato da string recebida, colocando data e hora que a mensagem foi gerada, pulando uma linha a cada mensagem
                    case DumpType.Type.Trace:
                        DumpTraceData dumpTraceData = (DumpTraceData)msg.Body;
                        flatFileWriter = DumpWriter.GetInstance(dumpTraceData.FolderName, dumpTraceData.FileName, dumpTraceData.Type);
                        e.Result = flatFileWriter.Write(dumpTraceData.DataToDump, dumpTraceData.TimeStamp);
                        break;

                    // Faz o dump exato do array de bytes recebido, sem nenhuma interferência no conteúdo.
                    case DumpType.Type.Stream:
                        DumpStreamData dumpStreamData = (DumpStreamData)msg.Body;
                        flatFileWriter = DumpWriter.GetInstance(dumpStreamData.FolderName, dumpStreamData.FileName, dumpStreamData.Type);
                        e.Result = flatFileWriter.Write(dumpStreamData.DataToDump, dumpStreamData.TimeStamp);
                        break;

                    case DumpType.Type.Poisoned:
                        DumpTraceData poisoned = DesmontaPoisoned(msg.Body.ToString());
                        flatFileWriter = DumpWriter.GetInstance(poisoned.FolderName, poisoned.FileName, poisoned.Type);
                        e.Result = flatFileWriter.Write(poisoned.DataToDump, poisoned.TimeStamp);
                        break;

                    // Não faz o dump e não tira a mensagem da fila, ao retornar false
                    default:
                        e.Result = false;
                        break;
                }

                _totalMessageSentPerInterval++;
            }
            catch (Exception ex)
            {                
                EventLog.WriteEntry(LogSources.DumperService, string.Concat(ex.Message, " ", ex.StackTrace), EventLogEntryType.Error);
                e.Result = false;
            }
        }        

        private DumpTraceData DesmontaPoisoned(string expression)
        {
            DumpTraceData ret = new DumpTraceData();

                int ini = 0;
                int fim = expression.IndexOf("|");
                ret.TimeStamp = CoverteData(expression.Substring(ini, fim));
                
                ini = fim +1;
                fim = expression.IndexOf("|", ini);
                ret.FolderName = expression.Substring(ini, fim - ini);
                
                ini = fim +1;
                fim = expression.IndexOf("|", ini);
                ret.FileName = expression.Substring(ini, fim -ini);

                ini = fim +1;
                fim = expression.Length;
                ret.DataToDump = expression.Substring(ini, fim - ini);

            return ret;
        }

        private DateTime CoverteData(string p)
        {
            return new DateTime(long.Parse(p));
        }


        //_msmqReceiver_OnHealthStatusChanged - Chamado pelo msmqReceiver, para informar a saúde do componente
        private void _msmqReceiver_OnHealthStatusChanged(MsmqReceiver msmqReceiver)
        {
            if (_dumperShdms != null)
            {
                lock (_dumperShdms)
                {
                    // Informações de sáude do componente que está conectado ao MSMQ 
                    // são colocadas na área de memória compartilhada
                    _dumperShdms.DumperDataInfo.MsmqDumper.QueuePath = msmqReceiver.Path;
                    _dumperShdms.DumperDataInfo.MsmqDumper.QueuedMessages = msmqReceiver.QueuedMessages;
                    _dumperShdms.DumperDataInfo.MsmqDumper.QueueBytesSize = msmqReceiver.QueueSizeBytes;
                    _dumperShdms.DumperDataInfo.MsmqDumper.QueueAcessible = msmqReceiver.IsConnected;
                    _dumperShdms.DumperDataInfo.MsmqDumper.MessagesPerSecond = msmqReceiver.MessagesReceivePerSecond;
                    _dumperShdms.DumperDataInfo.MsmqDumper.LimitQueueAcessible = true;
                    _dumperShdms.DumperDataInfo.MsmqDumper.LimitQueueBytesSize = long.MaxValue;
                    _dumperShdms.DumperDataInfo.MsmqDumper.LimitQueuedMessages = long.MaxValue;
                }
            }
        }


        //_healthMonitorCollector_HealthRequestListener - Chamado pelo HealthStatusCollector a cada "N" segundos
        private object _healthMonitorCollector_HealthRequestListener()
        {
            if (_dumperShdms != null)
            {
                lock (_dumperShdms)
                {
                    // Informações de sáude do serviço
                    // são colocadas na área de memória compartilhada
                    _dumperShdms.DumperDataInfo.SavedMessagesPerSecond = CalculaNumeroMsgsPorSegundo();
                    _dumperShdms.DumperDataInfo.DumpRootPath = DumpConfiguration.DumpConfigurationManager.DumperConfiguration.RootPath;

                    Process currentProcess = Process.GetCurrentProcess();

                    _dumperShdms.DumperDataInfo.ProcessDataInfo.PrivateMemory = currentProcess.PrivateMemorySize64;
                    _dumperShdms.DumperDataInfo.ProcessDataInfo.HandleCount = currentProcess.HandleCount;
                    _dumperShdms.DumperDataInfo.ProcessDataInfo.ThreadCount = currentProcess.Threads.Count;
                    _dumperShdms.DumperDataInfo.ProcessDataInfo.PrivilegedProcessorTime = currentProcess.PrivilegedProcessorTime.ToString();
                    _dumperShdms.DumperDataInfo.ProcessDataInfo.InitializedDate = currentProcess.StartTime;

                    _dumperShdms.DumperDataInfo.ProcessDataInfo.ThreadId = Thread.CurrentThread.ManagedThreadId.ToString();
                    _dumperShdms.DumperDataInfo.ProcessDataInfo.ProcessId = currentProcess.Id.ToString();

                    //Machine
                    if (String.IsNullOrEmpty(_dumperShdms.DumperDataInfo.ProcessDataInfo.MachineName))
                        _dumperShdms.DumperDataInfo.ProcessDataInfo.MachineName = Dns.GetHostName();

                    if (String.IsNullOrEmpty(_dumperShdms.DumperDataInfo.ProcessDataInfo.MachineIPAddress))
                        _dumperShdms.DumperDataInfo.ProcessDataInfo.MachineIPAddress = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName()).AddressList[0].ToString();
                }
            }

            // O conteúdo da memória compartilhada é entregue ao HealthStatusCollector
            return _dumperShdms;
        }

        private int CalculaNumeroMsgsPorSegundo()
        {
            int seconds;

            // Calcula o número de segundos entre a última coleta e a coleta atual
            seconds = DateTime.Now.Subtract(_lastHealthStatusChecked).Seconds;

            if (seconds == 0)
                seconds = 1;

            if (_totalMessageSentPerInterval >= seconds)
                _messagesPerSecond = _totalMessageSentPerInterval / seconds;
            else
                _messagesPerSecond = _totalMessageSentPerInterval;

            // Zera o contador que será usado na próxima coleta
            // Esse contador é incrementado dentro do OnMessageArrival(Message message)
            // a cada mensagem recebida que é escrita em banco
            _totalMessageSentPerInterval = 0;

            // Define o horário dessa verificação, para ser usada na próxima chamada da função e calcular os segundos entre uma chamada e outra
            _lastHealthStatusChecked = DateTime.Now;

            return _messagesPerSecond;
        }

        public void Dispose()
        {
            try
            {

                if (_msmqReceiver != null)
                {
                    _msmqReceiver.Dispose();
                    _msmqReceiver = null;
                }

                if (_dumperShdms != null)
                {
                    _dumperShdms.Dispose();
                    _dumperShdms = null;
                }

                if (_healthMonitorCollector != null)
                {
                    _healthMonitorCollector.RemoveHealthRequestListener(HealthType.Dumper);
                    _healthMonitorCollector.Dispose();
                    _healthMonitorCollector = null;
                }

                DumpWriter.Destroy();
            }
            catch (Exception ex)
            {
                EventLog.WriteEntry("DumperWrite", ex.Message);
                EventLog.WriteEntry("DumperWrite", ex.StackTrace);
            }
        }
    }
}
